package com.seadragon.apps.doctorcard.service.impl;

import static com.seadragon.apps.doctorcard.model.DoctorCardConstants.*;
import static org.apache.commons.lang3.StringUtils.isEmpty;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.codec.binary.Base64;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.seadragon.apps.doctorcard.model.Contact;
import com.seadragon.apps.doctorcard.model.DoctorCardResult;
import com.seadragon.apps.doctorcard.model.Request;
import com.seadragon.apps.doctorcard.model.RequestStatus;
import com.seadragon.apps.doctorcard.repository.ContactRepository;
import com.seadragon.apps.doctorcard.repository.RequestRepository;
import com.seadragon.apps.doctorcard.service.CacheService;
import com.seadragon.apps.doctorcard.service.ContactService;

@Service("contactService")
public class ContactServiceImpl implements ContactService {
	private static Logger logger = LoggerFactory.getLogger(ContactServiceImpl.class);
	@Autowired
	private ContactRepository contactRepository;

	@Autowired
	private RequestRepository requestRepository;

	@Autowired
	private CacheService cacheService;

	private MessageDigest md;

	public ContactServiceImpl(){
		try {
			md = MessageDigest.getInstance("SHA-256");
		} catch (NoSuchAlgorithmException ex) {
			logger.error("Failed to create MessageDigest", ex);
		}
	}
	
	@Override
	@Transactional
	public Contact save(Contact contact) {
		contact.setUuid(cacheService.getUUID());
		if(contact.getPassword() != null){ 
			contact.setPassword(encrypt(contact.getPassword()));
		}
		
		contact.setDateCreated(new Date());
		Contact contactSaved = contactRepository.save(contact);
		if(contactSaved != null)
			cacheService.updateCache(contactSaved);
		return contactSaved;
	}
	
	@Override
	public boolean isContactExisted(Contact contact){
		if(contact == null) return false;
		Contact contactSaved = null;
		if(isEmpty(contact.getEmail())){
			if(isEmpty(contact.getDoctorCardId())){
				if(isEmpty(contact.getMobile())){
					return true;
				}else{
					contactSaved = contactRepository.findByMobile(contact.getMobile());
				}
			}else{
				if(isEmpty(contact.getMobile())){
					contactSaved = contactRepository.findByDoctorCardIdIgnoreCase(contact.getDoctorCardId());
				}else{
					contactSaved = contactRepository.findByDoctorCardIdAndMobileIgnoreCase(contact.getDoctorCardId(), contact.getMobile());
				}
			}
		}else{
			if(isEmpty(contact.getDoctorCardId())){
				if(isEmpty(contact.getMobile())){
					contactSaved = contactRepository.findByEmailIgnoreCase(contact.getEmail());
				}else{
					contactSaved = contactRepository.findByMobileAndEmailIgnoreCase(contact.getMobile(), contact.getEmail());
				}
			}else{
				if(isEmpty(contact.getMobile())){
					contactSaved = contactRepository.findByDoctorCardIdAndEmailIgnoreCase(contact.getDoctorCardId(), contact.getEmail());
				}else{
					contactSaved = contactRepository.findByDoctorCardIdAndMobileAndEmailIgnoreCase(contact.getDoctorCardId(), contact.getMobile(), contact.getEmail());
				}
			}
		}
		
		if(contactSaved != null) return true;
		
		return false;
	}
	
	private final String encrypt(String password) {
		byte[] raw = md.digest(password.getBytes());
		return new String(Base64.encodeBase64(raw));
	}

	@Override
	@Transactional
	public DoctorCardResult changePassword(Contact contact, String oldPassword, String newPassword){
		DoctorCardResult result = new DoctorCardResult();
		if(isEmpty(newPassword)){
			result.setStatus(RETURN_FAIL);
			result.addErrorCode(ERROR_CODE_EMPTY_PASSWORD);
		}else{
			if(encrypt(oldPassword).equals(contact.getPassword())){
				contact.setPassword(encrypt(newPassword));
				Contact contactSaved = contactRepository.save(contact);
				if(contactSaved == null){
					result.setStatus(RETURN_FAIL);
					result.addErrorCode(ERROR_CODE_FAIL_TO_SAVE_CONTACT);
				}else{
					result.setStatus(RETURN_SUCCESS);
					result.setLoginedContact(contactSaved);
				}
			}else{
				result.setStatus(RETURN_FAIL);
				result.addErrorCode(ERROR_CODE_WRONG_CURRENT_PASSWORD);
			}
		}
		return result;
	}
	
	@Override
	@Transactional
	public void update(Contact contact) {
		contactRepository.save(contact);
	}

	@Override
	public Contact findById(long id) {
		return contactRepository.findOne(id);
	}

	@Override
	public Contact findByMobile(String mobile) {
		return contactRepository.findByMobile(mobile);
	}

	@Override
	public Contact findByUUID(String uuid) {
		return contactRepository.findByUuid(uuid);
	}

	@Override
	public Contact findByDoctorCardId(String doctorCardId) {
		return contactRepository.findByDoctorCardIdIgnoreCase(doctorCardId);
	}

	@Override
	public List<Contact> findByName(String name) {
		String likeName = "%" + name + "%";
		List<Contact> matches = contactRepository.findByNameLikeIgnoreCase(likeName);
		if(matches == null || matches.size() == 0){
			matches = contactRepository.findByEnglishNameLikeIgnoreCase(likeName);
		}
		return matches;
	}
	
	@Override
	public Contact login(String username, String password){
		List<Contact> contacts = contactRepository.findByDoctorCardIdOrMobileOrEmailIgnoreCase(username, username, username);
		if(contacts == null || contacts.size() == 0)
			return null;
		for(Contact contact : contacts){
			if(contact != null && contact.getPassword() != null && contact.getPassword().equals(encrypt(password))){
				if(contact.getToken() != null  || contact.getTimeExpired() != null){
					contact.setToken(null);
					contact.setTimeExpired(null);
					contactRepository.save(contact);
				}
				return contact;
			}
		}
		return null;
	}
	
	@Override
	public boolean isEmailExisted(String email, Contact excludeContact){
		Contact contact = contactRepository.findByEmailIgnoreCase(email);
		if(contact == null||(excludeContact!=null&&excludeContact.getId()==contact.getId())){
			return false;
		}
		
		return true;
//		return cacheService.isEmailExisted(email);
	}
	
	@Override
	public boolean isDoctorCardIdExisted(String doctorCardId, Contact excludeContact){
		Contact contact = contactRepository.findByDoctorCardIdIgnoreCase(doctorCardId);
		if(contact == null||(excludeContact!=null&&excludeContact.getId()==contact.getId())){
			return false;
		}
		
		return true;
//		return cacheService.isDoctorCardIdExisted(doctorCardId);
	}
	
	@Override
	public boolean isMobileExisted(String mobile, Contact excludeContact){
		Contact contact = contactRepository.findByMobile(mobile);
		if(contact == null||(excludeContact!=null&&excludeContact.getId()==contact.getId())){
			return false;
		}
		
		return true;
//		return cacheService.isMobileExisted(mobile);
	}
	
	@Override
	public boolean isTokenExisted(String token){
		Contact contact = contactRepository.findByToken(token);
		if(contact == null){
			return false;
		}
		
		return true;
//		return cacheService.isMobileExisted(mobile);
	}
	
	@Override
	public List<String> findAllUuid(){
		return contactRepository.findAllUuid();
	}
	
	@Override
	public Page<Contact> findAll(Pageable pageable){
		return contactRepository.findAll(pageable);
	}

	@Override
	public Contact findByEmail(String email) {
		return contactRepository.findByEmailIgnoreCase(email);
	}

	@Override
	public Contact findByToken(String token) {
		return contactRepository.findByToken(token);
	}
	
	@Override
	@Transactional
	public Contact forgotPassword(String email){
		Contact contact = contactRepository.findByEmailIgnoreCase(email);
		if(contact != null){
			contact.setToken(UUID.randomUUID().toString());
			Calendar now = Calendar.getInstance();
			now.add(Calendar.MINUTE, PERIOD_TO_EXPIRE_RESET_PASSWORD_LINK);
			contact.setTimeExpired(now.getTime());
			contactRepository.save(contact);
		}
		return contact;
		
	}
	
	@Override
	public List<Contact> search(Contact contact, String keyword){
		List<Contact> searchResults = contactRepository.findByNameContainingIgnoreCaseOrEnglishNameContainingIgnoreCase(keyword, keyword);
		Map<Long, Request> requestMap = new HashMap<Long, Request>();
		List<Request> requests = requestRepository.findAllRequests(contact);
		for(Request request : requests){
			if(contact.getId() == request.getSender().getId()){
				requestMap.put(request.getRecipient().getId(), request);
			}else{
				requestMap.put(request.getSender().getId(), request);
			}
		}
		for(Contact c : searchResults){
			Request request = requestMap.get(c.getId());
			if(request == null){
				c.setStatus(RequestStatus.NoRequest);
			}else{
				if(c.getId() == request.getSender().getId() && request.getStatus() == RequestStatus.RequestSent){
					c.setStatus(RequestStatus.RequestReceived);
					c.setMessage(request.getMessage());
				}else{
					c.setStatus(request.getStatus());
				}
			}
		}
		return searchResults;
	}
	
	@Override
	@Transactional
	public int resetPassword(String token, String password){
		Contact contact = contactRepository.findByToken(token);
		if(contact == null){
			return ERROR_CODE_RESET_PASSWORD_TOKEN_INVALID;
		}
		Date now = new Date();
		if(now.after(contact.getTimeExpired())){
			return ERROR_CODE_RESET_PASSWORD_TOKEN_EXPIRED;
		}
		contact.setToken(null);
		contact.setTimeExpired(null);
		contact.setPassword(encrypt(password));
		contactRepository.save(contact);
		return RETURN_SUCCESS;
	}
}
